![]() |
![]() |
|
Dieser triviale Lösungsansatz ist durchaus realisierbar, aber aus programmiertechnischer Sicht alles andere als optimal. Er bereitet nämlich Probleme, die bei genauerer Analyse zutage treten:
Den Zähler als Instanzvariable zu definieren, ist demnach kein guter Ansatz und bei den beschriebenen Nachteilen nicht akzeptabel. Aber wo liegt der grundlegende Fehler der Überlegung? Die Antwort auf die Frage ist recht einfach: Instanzvariablen sind objektgebunden. Um den Objektzähler zu realisieren, brauchen wir aber ein Feld, losgelöst von jedem konkreten Objekt, das nur in einer festen Bindung zur Klasse Circle steht. Der Objektzähler wäre damit als eine gemeinsame Eigenschaft aller Objekte dieses Typs zu betrachten. Probleme dieser Art, allen typgleichen Objekten klassen-, aber nicht objektgebundene Elemente zur Verfügung zu stellen, werden von C# durch das Schlüsselwort static gelöst. Bezogen auf die Forderung nach einem Objektzähler, würde ein erster Ansatz zur Problemlösung wie folgt aussehen:
Die standardmäßige Sichtbarkeit statischer Komponenten ist private. Die Sichtbarkeit kann durch die Modifizierer public, protected, internal und private an die Anforderungen angepasst werden. 5.1.2 Zugriff auf statische Komponenten
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int x = Circle.CountCircles; |
auf den Inhalt zugreifen.
Auch Methoden können static sein. Für den Aufruf einer Klassenmethode gilt wie für ein statisches Feld, dass vor dem Methodenbezeichner der Klassenname angegeben werden muss. Klassenmethoden wurden schon häufig in den Beispielcodes dieses Buches benutzt: Es sind die Methoden WriteLine und ReadLine, die von der Klasse Console bereitgestellt werden.
Sind in einer Klasse sowohl statische als auch objektbezogene Eigenschaften und Methoden definiert, unterliegt der wechselseitige Zugriff der beiden Elementtypen den folgenden zwei Regeln:
| Aus einer Instanzmethode heraus lassen sich Klassenvariablen manipulieren und Klassenmethoden aufrufen. Der umgekehrte Weg, nämlich aus einer statischen Methode heraus auf die Instanzeigenschaften und Instanzmethoden zuzugreifen, ist nicht möglich. |
Für statische Felder gelten dieselben Regeln der Datenkapselung wie für Instanzvariablen. Eine Klassenvariable wie CountCircles sollte daher in derselben Weise gekapselt werden, um nicht mit dem objektorientierten Paradigma zu brechen. Dazu wird sie private deklariert. Um den Zugriff von außerhalb sicherzustellen, implementieren wir in der Klasse Circle zusätzlich eine statische Eigenschaftsmethode. Damit eine Manipulation des Zählers von außerhalb unterbunden wird, muss die Eigenschaft durch Verzicht auf den set-Accessor schreibgeschützt sein.
|
|
| public class Circle { |
| // ---------- statische Felder -------------- |
| private static int countCircles; |
| // ---------- Klasseneigenschaften ---------- |
| public static int CountCircles { |
| get {return countCircles;} |
| } |
| ... |
| } |
Nun enthält die Circle-Klasse den angestrebten Objektzähler, der beliebig abgefragt werden kann. Allerdings ist die Klassendefinition noch unvollständig, denn es fehlt die Programmlogik, um den Zähler mit jeder neuen Objektinstanz zu erhöhen. Dazu bieten sich die Konstruktoren an. Hierbei nutzen wir wieder die Konstruktorverkettung und erhöhen den Objektzähler nur im parameterlosen Konstruktor, der aus den beiden anderen Initialisierungsroutinen aufgerufen wird:
| // ---------- Konstruktoren ---------- |
| public Circle() { |
| countCircles++; |
| } |
| public Circle(double Radius) : this() { |
| this.Radius = Radius; |
| } |
| public Circle(double Radius, int X, int Y) : this(Radius) { |
| this.XKoordinate = X; |
| this.YKoordinate = Y; |
| } |
Die Implementierung der Zählerreduzierung ist nicht ganz so einfach. Bekanntlich zeichnet sich die Zerstörung einer Instanz durch die .NET-Laufzeitumgebung dadurch aus, dass der Garbage Collector einen nicht deterministischen Dienst versieht. Mit anderen Worten: Es kann nicht vorhergesagt werden, wann er aktiv wird. Prinzipiell bieten sich daher zwei Lösungsansätze an:
1. Das statische Feld countCircles wird nur im Destruktor reduziert. Allerdings muss dabei in Kauf genommen werden, dass der Inhalt unter Umständen nicht den aktuellen Stand der gültigen Circle-Objekte widerspiegelt.| 2. | Dem Benutzer der Klasse wird eine Methode angeboten, von welcher der Benutzer weiß, dass sie für die Aktualisierung des Zählers sorgt, bevor ein Objekt aufgegeben wird. Dabei kann es sich natürlich um eine x-beliebige Methode handeln. Gut ist es, sich daran zu erinnern, dass die Dispose-Methode der Schnittstelle IDisposable definitionsgemäß solche Aufgaben übernehmen soll. Diese Lösung ist jedoch auch nicht optimal, denn versäumt der Benutzer den Aufruf der Methode, ist der Zählerstand nicht aktuell, und wir müssen auf den Garbage Collector warten. |
Beide Einzellösungen sind unbefriedigend und der Preis, den wir für den automatischen Aufräummechanismus des Garbage Collectors zahlen müssen. Daher erscheint die Formulierung einer Synthese aus beiden Lösungsansätzen den Anforderungen noch am nächsten zu kommen:
| class Circle : IDisposable { |
| // boolesche Variable, die Destruktor und Dispose steuert |
| private bool counterReduced = false; |
| ... |
| // Destruktor |
| ~Circle() { |
| if( ! counterReduced) |
| countCircles--; |
| } |
| // Dispose aus der IDisposable-Schnittstelle |
| public void Dispose() { |
| if( ! counterReduced) { |
| countCircles--; |
| counterReduced = true; |
| } |
| } |
| } |
Um den internen Zähler countCircles nicht zweimal herabzusetzen, ist zur Verfolgung des Freigabezustands eines Objekts die private Variable counterReduced deklariert. Bei einem ordnungsgemäßen Aufruf der Methode Dispose wird der Objektzähler reduziert und counterReduced auf true gesetzt. Der Code in der Dispose-Methode ist so entwickelt, dass ein versehentlich wiederholter Aufruf von Dispose kein weiteres Mal den Objektzähler reduziert. Ruft der Speicherbereinigungsmechanismus anschließend den Destruktor des Objekts auf, bleibt der Zähler unverändert, weil die Bedingung zur Ausführung der Anweisung nicht erfüllt ist. Vergisst der Client den Aufruf von Dispose, behält das Flag counterReduced seinen Initialisierungswert, und der Garbage Collector wird für die Aktualisierung des Objektzählers sorgen.
Neben den statischen Klasseneigenschaften können auch klassenbezogene Methoden definiert werden. Klassenmethoden sind unabhängig von einer bestimmten Klasseninstanz und werden ebenfalls mit dem Modifizierer static gekennzeichnet. Typischerweise werden statische Methoden da eingesetzt, wo nicht mit objektspezifischen Daten gearbeitet wird oder allgemein gültige Operationen angeboten werden sollen. Ein typischer Vertreter des .NET Frameworks ist die Klasse Math. In dieser sind alle Methoden statisch.
Methoden gelten als überladen, wenn sie sich in der Parameterliste unterscheiden. An dieser Regel ändert auch der Modifizierer static nichts. Wenn Sie also versuchen, die Methode
| public void TestMethod() {/*...*/} |
mit
| public static void TestMethod() {/*...*/} |
zu überladen, wird die Kompilierung abgelehnt.
Weiter oben wurden zwei Regeln aufgestellt: Instanzmethoden können auf die statischen Mitglieder einer Klasse zugreifen, Klassenmethoden sind jedoch objektunabhängig und haben daher keinen Zugriff auf Instanzmember. Die Klassendefinition TestClass im folgenden Codebeispiel verdeutlicht diesen Sachverhalt. Die Instanzmethode EditVariable manipuliert sowohl die Instanzvariable lngVar als auch die Klassenvariable intVar, die Klassenmethode ClassMethod hat nur den Zugriff auf die allen Objekten der Klasse gemeinsame Eigenschaft intVar, nicht jedoch auf die objektspezifische lngVar.
| class TestClass { |
| public long lngVar; |
| public static int intVar; |
| // Instanzmethode |
| public void EditVariable() { |
| lngVar = 24; |
| intVar = 4711; |
| } |
| // Klassenmethode |
| public static void ClassMethod() { |
| // ACHTUNG: unzulässiger Zugriff auf die Instanzvariable |
| lngVar = 100; |
| } |
| } |
Ehe wir die Klasse Circle um weitere Methoden ergänzen, wollen wir zunächst zwei Methoden zur Diskussion stellen, die bereits im Kapitel 4 entwickelt worden sind:
| public double GetFlaeche(double radius) { |
| return PI * Math.Pow(radius, 2); |
| } |
| public double GetUmfang(double radius) { |
| return 2 * PI * radius; |
| } |
Beide Instanzmethoden haben allgemein gültigen Charakter, denn die erforderlichen Dateninformationen werden nicht aus dem Objekt bezogen, sondern über einen Parameter den Methoden mitgeteilt. Damit sind GetFlaeche und GetUmfang genau genommen objektunabhängig und sollten durch Ergänzung des Modifizierers static zu statischen Methoden erklärt werden:
| public static double GetFlaeche(double radius) { |
| return Circle.PI * Math.Pow(radius, 2); |
| } |
| public static double GetUmfang(double radius) { |
| return 2 * Circle.PI * radius; |
| } |
Darüber hinaus soll die Klasse Circle um Methoden ergänzt werden, die in der Lage sind, zwei Kreisobjekte miteinander zu vergleichen. Das Ergebnis des Vergleichs sei die Rückgabe der Referenz auf das größere der beiden Circle-Objekte. Haben beide Kreise denselben Radius, soll der Rückgabewert die Referenz auf das im ersten Argument aufgeführte Objekt sein. Wir werden dazu sowohl Instanz- als auch Klassenmethoden bereitstellen.
Fangen wir mit der Instanzmethode an. Der Code dazu lautet wie folgt:
| public Circle Bigger(Circle kreis) { |
| if(this.radius >= kreis.radius) |
| return this; |
| return kreis; |
| } |
Bigger prüft zuerst die Bedingung und benutzt dazu das Schlüsselwort
| this |
Beachten Sie, dass
| this |
nur für Instanzvariablen und Instanzmethoden zur Verfügung steht und bei statischen Klassenmitgliedern weder Sinn macht noch erlaubt ist. Die
| if |
Bedingung dürfte auch
| if(radius >= kreis.radius)... |
lauten, um den Inhalt des Feldes radius des aktuellen Objekts auszuwerten. Die Angabe der this-Referenz ist im vorliegenden Fall nicht unbedingt notwendig, weil die Eindeutigkeit von radius auch ohne diese Angabe gewährleistet ist. Auf das Feld radius der übergebenen Referenz kreis ist der Zugriff trotz der private-Deklaration möglich, weil der Zugriff auf die privaten Member eines typgleichen Objekts gestattet ist.
Weniger empfehlenswert ist, über den Aufruf der beiden Eigenschaftsmethoden Radius die beiden Größen zu vergleichen, also:
| if(this.Radius >= kreis.Radius)... |
Die Folge wäre der Aufruf des get-Accessors in der Eigenschaftsmethode. Das ist jedoch unnötig, weil das gekapselte Feld radius in jedem Fall einen gültigen Wert enthält.
Angenommen, die beiden Objektreferenzen Kreis1 und Kreis2 sind vom Typ Circle, könnten wir Bigger auf das Objekt Kreis1 wie folgt aufrufen:
Circle newCircle = Kreis1.Bigger(Kreis2)
Die Referenz auf das größere der beiden Objekte wird der Objektvariablen newCircle zugewiesen oder – falls die beiden Kreise gleich groß sind – entsprechend der Methodenimplementierung die Referenz auf das Objekt, in dessen Kontext der Aufruf der Methode erfolgt.
Bigger ist nicht in der Lage, einen Benutzer über eine gegebenenfalls vorliegende radielle Gleichheit zu informieren. Das soll eine Überladung sicherstellen. Da wir dann allerdings auch zwei Ergebnisse an den Aufrufer zurückliefern, müssen wir die Parameterliste um einen out-Parameter ergänzen.
| public Circle Bigger(Circle Kreis, out bool equal) { |
| equal = false; |
| if(this.radius == Kreis.radius) |
| equal = true; |
| return this.Bigger(Kreis); |
| } |
Über equal wird dem Aufrufer bei ungleichen Radien false, ansonsten true zurückgeliefert. Die Prüfung der Radien beider Referenzen erfolgt über die vorher definierte einparametrige Bigger-Methode.
Wir wollen uns nun den Aufruf der zweiparametrigen Bigger-Methode an einem Codefragment ansehen.
| static void Main(string[] args) { |
| bool equality; |
| Circle kreis1 = new Circle(32); |
| Circle kreis2 = new Circle(18); |
| Circle maxKreis = kreis1.Bigger(kreis2, out equality); |
| if(equality) |
| Console.WriteLine("Beide Kreise sind gleich gross"); |
| else |
| Console.WriteLine("Beide Kreise haben verschiedene Radien"); |
| Console.Write("Radius (max) = {0}", maxKreis.Radius); |
| } |
Interessant ist die Auswertung des zweiten Parameters. In Main wird dazu die lokale Variable equality deklariert. Beim Methodenaufruf wird die Referenz auf diese Variable mit out an Bigger übergeben. Bigger-Methode schreibt das Ergebnis der Prüfung in den Parameter equal. Der aufrufende Code ist in der Lage, dieses über seine lokale Variable equality auszuwerten.
Dasselbe Verhalten sollen jetzt auch noch zwei Klassenmethoden aufweisen. Weil der Aufruf einer Klassenmethode immer auf dem Klassennamen erfolgt, müssen die beiden zu vergleichenden Circle-Objekte an die Parameterliste übergeben werden.
| public static Circle Bigger(Circle Kreis1, Circle Kreis2) { |
| if(Kreis1.radius >= Kreis2.radius) |
| return Kreis1; |
| return Kreis2; |
| } |
| public static Circle Bigger(Circle Kreis1, Circle Kreis2, out bool equal) { |
| equal = false; |
| if(Kreis1.radius == Kreis2.radius) |
| equal = true; |
| return Circle.Bigger(Kreis1, Kreis2); |
| } |
Alle bisher implementierten Bigger-Methoden liefern dem Aufrufer jeweils die Referenz auf ein Circle-Objekt zurück. Wenn der Vergleich in einer Kontrollstruktur wie der bedingten Anweisung erfolgt, ist eher ein boolescher Rückgabewert von Interesse. Nehmen wir an, wir wollen nur wissen, ob der Radius einer Referenz größer ist als der Radius einer zweiten Referenz, und nehmen wir weiter an, dass die Methode, die einen bool liefert, IsBigger heißt und als Klassenmethode definiert ist. Dann könnte mit der Anweisung
if(Circle.IsBigger(Kreis1, Kreis2))...
der einfache Vergleich erfolgen.
Um diese Bedingungsprüfung durchführen zu können, wollen wir jetzt IsBigger implementieren. Wir legen dabei fest, dass der Methodenaufruf true zurückliefern soll, falls der Radius des Kreises des ersten Parameters größer oder gleich dem Kreis des zweiten Parameters ist. Das entspricht der Bedingung
Kreis1.Radius >= Kreis2.Radius
Mit dieser Vorgabe sieht die statische Methode wie folgt aus:
| public static bool IsBigger(Circle Kreis1, Circle Kreis2) { |
| if(Kreis1.radius >= Kreis2.radius) |
| return true; |
| return false; |
| } |
Selbstverständlich bestünde auch die Möglichkeit, zusätzlich eine Instanzmethode namens IsBigger anzubieten. Der Programmcode unterscheidet sich aber nur geringfügig von dem der hier gezeigten statischen Variante und trägt nicht weiter zum Verständnis bei. Daher wird an dieser Stelle darauf verzichtet.
Nach dem aktuellen Stand enthält die Klasse Circle mehrere Instanzfelder und eine Klassenvariable:
| private int xKoordinate = 0; |
| private int yKoordinate = 0; |
| private double radius = 0; |
| private bool counterReduced = false; |
| private static int countCircles; |
Klassen- und Instanzvariablen werden zu unterschiedlichen Zeitpunkten initialisiert. Dabei gilt die folgende Regel:
| Instanzvariablen werden initialisert, wenn bei der Instanziierung mit new ein Konstruktor aufgerufen wird. Klassenvariablen hingegen werden initialisiert, wenn die Klasse zum ersten Mal geladen wird. Das kann sein, wenn erstmalig auf ein statisches Klassenmitglied zugegriffen wird oder bevor das erste Objekt der Klasse erstellt wird, ohne dass vorher ein Zugriff auf ein statisches Element erfolgte. |
Praktisch bedeutet das, dass uns bereits mit
| Console.WriteLine(Circle.CountCircles); |
ein Ergebnis angezeigt wird, ohne dass vorher ein Objekt dieses Typs erzeugt worden ist, während andererseits eine Instanzvariable grundsätzlich immer die Existenz eines konkreten Objekts voraussetzt.
Bei der Instanziierung einer Klasse wird ein Konstruktor aufgerufen. Auf Klassenbasis gibt es dazu ein Pendant, das als statischer Konstruktor oder statischer Initialisierer bezeichnet wird. Der statische Konstruktor ist eine an die Klasse gebundene Methode, die nur auf die statischen Mitglieder der Klasse Zugriff hat.
Der statische Konstruktor einer Klasse wird aufgerufen, bevor die erste Instanz erstellt wird oder der erste Zugriff auf ein statisches Klassenmitglied erfolgt. Die Definition eines statischen Konstruktors sieht folgendermaßen aus:
| // Syntax des statischen Konstruktors |
| static Klassenbezeichner() {...} |
Beachten Sie, dass ein statischer Konstruktor, ähnlich wie der Destruktor, keinen Zugriffsmodifizierer akzeptiert. Da ein statischer Konstruktor automatisch aufgerufen wird und niemals direkt, macht eine Parameterliste keinen Sinn – die Klammern bleiben grundsätzlich leer.
Das folgende Codefragment zeigt den statischen Initialisierer einer fiktiven Klasse ClassA, welcher der Klassenvariablen str einen Wert zuweist.
|
|
| class ClassA { |
| // Klassenvariable |
| public static string str; |
| // statischer Konstruktor |
| static ClassA() { |
| str = "C# macht Spass."; |
| } |
| } |
Selbstverständlich könnten wir denselben Effekt erzielen, wenn wir die statische Variable direkt mit
| public static string str = "C# macht Spass."; |
initialisieren würden. Der statische Konstruktor bietet sich eher dazu an, komplexere Initialisierungen vorzunehmen. Dabei könnte es sich um das Auslesen von Dateien oder auch um die Initialisierung statischer Arrays handeln, wie das folgende Beispiel zeigt:
| // -------------------------------------------------------------- |
| // Beispiel: ...\Kapitel 5\StatischesArray |
| // -------------------------------------------------------------- |
| class Class1 { |
| static void Program(string[] args) { |
| for(int i = 0; i <= 100; i++) |
| Console.Write("myArr[{0}] = {1}\n", i, ClassA.myArr[i]); |
| Console.ReadLine(); |
| } |
| } |
| class ClassA { |
| // statisches Array |
| public static int[] myArr = new int[101]; |
| // statischer Konstruktor |
| static ClassA() { |
| Random rnd = new Random(); |
| for(int i = 0; i <= 100; i++) |
| myArr[i] = rnd.Next(101); |
| } |
| } |
Das Array myArr in der Klasse ClassA wird im statischen Konstruktor mit Werten zwischen 0 und 100 initialisiert, die nach dem Zufallsprinzip der Klasse Random gebildet werden. Zur Bestätigung erfolgt die Ausgabe des Array-Inhalts an der Konsole.
Statische Klasseninitialisierer und Konstruktoren sind sich in der Funktionsweise ähnlich. Während Klasseninitialisierer Klassendaten bereitstellen, versorgen Konstruktoren die objektspezifischen Felder mit Daten. Sobald Sie eine Objektvariable deklarieren, wird, soweit implementiert, der statische Konstruktor ausgeführt und erst danach der Konstruktor. Im Bedarfsfall dürfen Sie also im Konstruktor Code implementieren, der die vorhergehende Initialisierung der statischen Klassenmitglieder voraussetzt.
Es gibt Klassen, die nur statische Mitglieder enthalten. Meistens handelt es sich dabei um Klassen, die allgemein gültige Operationen bereitstellen. Im .NET Framework gibt es davon einige, System.Math gehört auch dazu. Typischerweise sind solche Klassen nicht instanziierbar, weil ein Objekt einer solchen Klasse funktionslos und damit letztendlich auch sinnlos ist. Nicht instanziierbare Klassen haben darüber hinaus einen private definierten, parameterlosen Konstruktor, um die Instanziierung von vornherein unmöglich zu machen.
Mit der Version 2.0 des .NET Frameworks wird die Entwicklung von Klassen, die nur statische Mitglieder bereitstellen, ein wenig einfacher, zumindest jedoch offensichtlicher. Der Modizierer static kann nun auch vor dem Schlüsselwort class angegeben werden, z.B.:
| public static class MathDefinitions { |
| public static double Addition(params double[] values) { |
| // Anweisungen |
| } |
| public static double Subtraktion(params double[] values) { |
| // Anweisungen |
| } |
| ... |
| } |
Geben Sie static als Modifizierer einer Klasse an, müssen Sie die folgenden Punkte beachten:
| Statische Klassen dürfen nur statische Klassenmitglieder veröffentlichen. Der Modizierer static ist auch bei den Membern anzugeben. |
| Statische Klassen enthalten keine Konstruktoren und können deshalb auch nicht instanziiert werden. Der parameterlose Konstruktor ist implizit private. |
| Statische Klassen können nicht abgeleitet werden. |
Der Aufruf statischer Klassen erfolgt ebenfalls unter Angabe des Klassenbezeichners, z.B.:
| MathDefinitions.Addition(2, 77, 99); |
| Statische Klassenkomponenten, gekennzeichnet durch den Modifizierer static, sind nicht an ein konkretes Objekt gebunden. Es gibt sowohl statische Felder als auch statische Eigenschaften und Methoden. Statische Klassenmitglieder werden über die Angabe des Klassennamens angesprochen. |
| Ein statisches Feld existiert nur einmal im Speicher, unabhängig davon, wie viele Klasseninstanzen erzeugt werden. |
| Klassenmethoden können weder Instanzvariablen manipulieren noch Instanzmethoden aufrufen. Andererseits haben Instanzmethoden die Möglichkeit, sowohl auf statische Felder zuzugreifen als auch statische Methoden auszuführen. |
| Die this-Referenz steht in statischen Methoden nicht zur Verfügung. |
| Klassenvariablen werden initialisiert, sobald der erste Zugriff auf die Klasse erfolgt. Die Initialisierung der Klassenvariablen kann über den statischen Konstruktor erfolgen. Dieser wird automatisch aufgerufen, hat keinen Zugriffsmodifizierer und grundsätzlich eine leere Parameterliste. |
| Wird erstmalig das Objekt eines Typs erzeugt, wird zuerst der statische Initialisierer aufgerufen und danach der Konstruktor zur Initialisierung der Objektdaten. |
| Statische Klassen werden in der Klassendefinition um den Modifizierer static ergänzt. Eine statische Klasse kann weder instanziiert noch abgeleitet werden und hat nur statische Klassenmitglieder. |
| << zurück |
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
Copyright © Galileo Press 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.